from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtGui
#from PyQt4 import Qwt5

import re
import logging
logging.basicConfig(level=logging.INFO)
#from logging import debug,info,warning,error


class AttrEditor(object):
    def __init__(self,ser,attr):
        self.ser,self.attr=ser,attr
        self.hot=False
        self.widget=None
    def refresh(self): pass
    def update(self): pass
    def isHot(self,hot=True):
        "Called when the widget gets focus; mark it hot, change colors etc."
        if hot==self.hot: return
        self.hot=hot
        p=QPalette();
        if hot: p.setColor(QPalette.WindowText,Qt.red);
        self.setPalette(p)
        self.repaint()
        print(self.attr,('hot' if hot else 'cold'))
    def sizeHint(self): return QSize(180,12)

class AttrEditor_Bool(AttrEditor,QCheckBox):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QCheckBox.__init__(self,parent)
        self.clicked.connect(self.update)
    def refresh(self):
        self.setChecked(getattr(self.ser,self.attr))
    def update(self): setattr(self.ser,self.attr,self.isChecked())

class AttrEditor_Int(AttrEditor,QSpinBox):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QSpinBox.__init__(self,parent)
        self.setRange(int(-1e10),int(1e10)); self.setSingleStep(1);
        self.valueChanged.connect(self.update)
    def refresh(self): self.setValue(getattr(self.ser,self.attr))
    def update(self): setattr(self.ser,self.attr,self.value()); self.isHot(False)

class AttrEditor_Str(AttrEditor,QLineEdit):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QLineEdit.__init__(self,parent)
        self.textEdited.connect(self.isHot)
        self.editingFinished.connect(self.update)
    def refresh(self): self.setText(getattr(self.ser,self.attr))
    def update(self): setattr(self.ser,self.attr,str(self.text())); self.isHot(False)

class AttrEditor_Float(AttrEditor,QLineEdit):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QLineEdit.__init__(self,parent)
        self.textEdited.connect(self.isHot)
        self.editingFinished.connect(self.update)
    def refresh(self): self.setText(str(getattr(self.ser,self.attr)))
    def update(self):
        try: setattr(self.ser,self.attr,float(self.text()))
        except ValueError: self.refresh()
        self.isHot(False)

class AttrEditor_Vector3(AttrEditor,QFrame):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QFrame.__init__(self,parent)
        self.setContentsMargins(0,0,0,0)
        self.box=QHBoxLayout(self); self.box.setSpacing(0); self.box.setMargin(0)
        self.widgets=[]
        for i in range(3):
            w=QLineEdit('');
            self.box.addWidget(w); self.widgets.append(w)
            w.textEdited.connect(self.isHot)
            w.editingFinished.connect(self.update)
    def refresh(self):
        val=getattr(self.ser,self.attr)
        for i in range(3): self.widgets[i].setText(str(val[i]))
    def update(self):
        try:
            val=getattr(self.ser,self.attr)
            for i in range(3):
                if self.widgets[i].isModified(): val[i]=float(self.widgets[i].text())
            print('setting',val)
            setattr(self.ser,self.attr,val)
        except ValueError: self.refresh()
        self.isHot(False)
class AttrEditor_ListStr(AttrEditor,QPlainTextEdit):
    def __init__(self,parent,ser,attr):
        AttrEditor.__init__(self,ser,attr)
        QPlainTextEdit.__init__(self,parent)
        self.setLineWrapMode(QPlainTextEdit.NoWrap)
        self.setFixedHeight(80)
        self.textChanged.connect(self.update)
    def refresh(self):
        lst=getattr(self.ser,self.attr)
        self.setPlainText('\n'.join(lst))
    def update(self):
        if self.hasFocus(): self.isHot()
        t=self.toPlainText()
        setattr(self.ser,self.attr,str(t).strip().split('\n'))
        if not self.hasFocus(): self.isHot(False)


class SerializableEditor(QWidget):
    "Class displaying and modifying serializable attributes of a woo object."
    import collections
    import logging
    # each attribute has one entry associated with itself
    class EntryData(object):
        def __init__(self,name,T):
            self.name,self.T=name,T
            self.lineNo,self.widget=None,None
    def __init__(self,ser,parent=None):
        "Construct window, *ser* is the object we want to show."
        QtGui.QWidget.__init__(self,parent)
        self.ser=ser
        self.entries=[]
        logging.debug('New Object of type %s'%ser.__class__.__name__)
        self.setWindowTitle(str(ser))
        self.mkWidgets()
        self.refreshTimer=QTimer()
        self.refreshTimer.timeout.connect(self.refreshEvent)
        self.refreshTimer.start(500)
    def getListTypeFromDocstring(self,attr):
        "Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works."
        doc=getattr(self.ser.__class__,attr).__doc__
        if doc==None:
            error("Attribute %s has no docstring."%attr)
            return None
        m=re.search(r':yattrtype:`([^`]*)`',doc)
        if not m:
            error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc))
            return None
        cxxT=m.group(1)
        logging.debug('Got type "%s" from :yattrtype:'%cxxT)
        def vecTest(T,cxxT):
            regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$'
            m=re.match(regexp,cxxT)
            return m
        vecMap={
            'int':int,'long':int,'Body::id_t':int,'size_t':int,
            'Real':float,'float':float,'double':float,
            'Vector3r':Vector3,'Matrix3r':Matrix3,
            'string':str
        }
        for T,ret in list(vecMap.items()):
            if vecTest(T,cxxT):
                logging.debug("Got type %s from cxx type %s"%(ret.__name__,cxxT))
                return (ret,)
        error("Unable to guess python type from cxx type '%s'"%cxxT)
        return None
    def mkAttrEntries(self):
        d=self.ser.dict()
        for attr,val in list(self.ser.dict().items()):
            if isinstance(val,list):
                if len(val)==0: t=self.getListTypeFromDocstring(attr)
                else: t=(val[0].__class__,) # 1-tuple is list of the contained type
            else: t=val.__class__
            #logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__)))
            self.entries.append(self.EntryData(name=attr,T=t))
    def mkWidget(self,entry):
        typeMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,Vector3:AttrEditor_Vector3,(str,):AttrEditor_ListStr}
        Klass=typeMap.get(entry.T,None)
        if not Klass: return None
        widget=Klass(self,self.ser,entry.name)
        widget.setFocusPolicy(Qt.StrongFocus)
        return widget
    def mkWidgets(self):
        self.mkAttrEntries()
        grid=QtGui.QGridLayout()
        for lineNo,entry in enumerate(self.entries):
            grid.addWidget(QtGui.QLabel(entry.name),lineNo,0)
            entry.widget=self.mkWidget(entry)
            if entry.widget: grid.addWidget(entry.widget,lineNo,1)
            else: grid.addWidget(QtGui.QLabel('<i>unhandled type</i>'),lineNo,1)
        self.setLayout(grid)
        self.refreshEvent()
    def refreshEvent(self):
        for e in self.entries:
            if e.widget and not e.widget.hot: e.widget.refresh()



vr=VTKRecorder()
s1=SerializableEditor(vr)
s1.show()
s2=SerializableEditor(OpenGLRenderer())
s2.show()
#import time
#def aa():
#    print vr.dict(); time.sleep(1)

